import tkinter as tk
from tkinter import scrolledtext, messagebox, filedialog
import os
import subprocess
import urllib.request
import zipfile
import shutil

class MinecraftLauncher:
    def __init__(self, root):
        self.root = root
        self.root.title("Minecraft 1.16 启动器")
        self.root.geometry("600x500")
        self.root.resizable(False, False)
        
        # 获取launcher.py所在目录的绝对路径
        self.launcher_dir = os.path.dirname(os.path.abspath(__file__))
        
        # 创建UI组件
        self.create_widgets()
        
        # 显示启动器目录
        self.add_log(f"启动器目录: {self.launcher_dir}")
        
        # 检查Java
        self.check_java()
        
        # 检查libs目录
        self.check_libs()
    
    def create_widgets(self):
        # 用户名标签和输入框
        tk.Label(self.root, text="用户名:").place(x=20, y=30)
        self.username_entry = tk.Entry(self.root, width=30)
        self.username_entry.place(x=100, y=30)
        
        # 皮肤设置
        tk.Label(self.root, text="皮肤:").place(x=20, y=60)
        self.skin_path_var = tk.StringVar()
        self.skin_entry = tk.Entry(self.root, textvariable=self.skin_path_var, width=30)
        self.skin_entry.place(x=100, y=60)
        self.browse_skin_button = tk.Button(self.root, text="浏览", command=self.browse_skin, width=8)
        self.browse_skin_button.place(x=320, y=60)
        
        # 启动模式选择
        tk.Label(self.root, text="启动模式:").place(x=20, y=90)
        self.launch_mode = tk.StringVar(value="basic")
        
        # 基础游玩模式
        tk.Radiobutton(self.root, text="基础游玩模式", variable=self.launch_mode, value="basic").place(x=100, y=90)
        tk.Label(self.root, text="(没声音,仅英文,仅保证基础游玩)", font=("Arial", 8), fg="gray").place(x=220, y=92)
        
        # 全量版
        tk.Radiobutton(self.root, text="全量版", variable=self.launch_mode, value="full").place(x=100, y=115)
        tk.Label(self.root, text="(完整的体验，需要占用更大的体积(300MB))", font=("Arial", 8), fg="gray").place(x=160, y=117)
        
        # 按钮
        self.launch_button = tk.Button(self.root, text="启动游戏", command=self.launch_minecraft, width=15)
        self.launch_button.place(x=100, y=145)
        
        self.download_libs_button = tk.Button(self.root, text="下载前置文件", command=self.download_libs, width=15)
        self.download_libs_button.place(x=220, y=145)
        
        self.download_java_button = tk.Button(self.root, text="下载Java", command=self.download_java, width=15)
        self.download_java_button.place(x=340, y=145)
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = tk.Scale(self.root, variable=self.progress_var, orient="horizontal", length=500, from_=0, to=100, showvalue=False)
        self.progress_bar.place(x=50, y=140)
        self.progress_label = tk.Label(self.root, text="进度: 0%")
        self.progress_label.place(x=250, y=120)
        
        # 日志区标签
        tk.Label(self.root, text="日志:").place(x=20, y=180)
        
        # 日志区
        self.log_text = scrolledtext.ScrolledText(self.root, width=65, height=12, wrap=tk.WORD)
        self.log_text.place(x=20, y=210)
        self.log_text.config(state=tk.DISABLED)
    
    def add_log(self, message):
        """添加日志信息"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)
    
    def check_java(self):
        """检查Java是否存在"""
        self.add_log("正在检查Java运行库...")
        # 使用launcher.py所在目录的相对路径
        java_path = os.path.join(self.launcher_dir, "jre-legacy", "bin", "javaw.exe")
        if os.path.exists(java_path):
            self.add_log(f"Java运行库已检测到: {java_path}")
            return True
        else:
            self.add_log(f"未检测到Java运行库: {java_path}")
            self.add_log("请点击'下载Java'按钮进行下载。")
            return False
    
    def check_libs(self):
        """检查libs目录是否存在"""
        self.add_log("正在检查前置文件...")
        # 使用launcher.py所在目录的相对路径
        libs_dir = os.path.join(self.launcher_dir, "libs")
        if os.path.exists(libs_dir):
            self.add_log("前置文件已检测到！")
            return True
        else:
            self.add_log("未检测到前置文件，请点击'下载前置文件'按钮进行下载。")
            return False
    
    def browse_skin(self):
        """浏览皮肤文件"""
        file_path = filedialog.askopenfilename(
            title="选择皮肤文件",
            filetypes=[("PNG文件", "*.png"), ("所有文件", "*")]
        )
        if file_path:
            self.skin_path_var.set(file_path)
            self.add_log(f"已选择皮肤文件: {file_path}")
    
    def download_file(self, url, filename):
        """自动下载文件"""
        self.add_log(f"开始下载: {url}")
        try:
            # 显示下载进度
            def reporthook(count, block_size, total_size):
                if total_size > 0:
                    percent = int(count * block_size * 100 / total_size)
                    if percent % 5 == 0:  # 每5%更新一次
                        self.add_log(f"下载进度: {percent}%")
                        self.progress_var.set(percent)
                        self.progress_label.config(text=f"进度: {percent}%")
                        self.root.update_idletasks()
            
            # 下载文件
            urllib.request.urlretrieve(url, filename, reporthook)
            self.add_log(f"下载完成: {filename}")
            self.progress_var.set(100)
            self.progress_label.config(text="进度: 100%")
            self.root.update_idletasks()
            return True
        except Exception as e:
            self.add_log(f"错误: 下载失败: {str(e)}")
            return False
    
    def extract_zip(self, zip_path, extract_path):
        """自动解压zip文件"""
        self.add_log(f"开始解压: {zip_path}")
        try:
            # 确保解压目录存在
            if not os.path.exists(extract_path):
                os.makedirs(extract_path)
            
            # 使用zipfile库解压
            with zipfile.ZipFile(zip_path, 'r') as zip_ref:
                # 获取文件总数
                total_files = len(zip_ref.namelist())
                extracted_files = 0
                
                for file_info in zip_ref.infolist():
                    # 解压文件
                    zip_ref.extract(file_info, extract_path)
                    extracted_files += 1
                    
                    # 显示解压进度
                    if total_files > 0:
                        percent = int(extracted_files * 100 / total_files)
                        if percent % 10 == 0:  # 每10%更新一次
                            self.add_log(f"解压进度: {percent}%")
                            self.progress_var.set(percent)
                            self.progress_label.config(text=f"进度: {percent}%")
                            self.root.update_idletasks()
            
            self.add_log(f"解压完成: {zip_path}")
            self.progress_var.set(100)
            self.progress_label.config(text="进度: 100%")
            self.root.update_idletasks()
            return True
        except zipfile.BadZipFile:
            self.add_log(f"错误: {zip_path} 不是有效的zip文件")
            return False
        except Exception as e:
            self.add_log(f"错误: 解压失败: {str(e)}")
            return False
    
    def download_libs(self):
        """下载前置文件"""
        self.add_log("开始下载前置文件...")
        
        # 下载libs.zip
        zip_filename = "libs.zip"
        zip_path = os.path.join(self.launcher_dir, zip_filename)
        if not self.download_file("https://beta18jdkdownlink.ak47s.com.cn/libs.zip", zip_path):
            self.add_log("前置文件下载失败，请检查网络连接后重试")
            return
        
        # 尝试解压
        extract_path = self.launcher_dir
        if not self.extract_zip(zip_path, extract_path):
            self.add_log("前置文件解压失败")
            return
        
        # 检查解压是否成功
        if self.check_libs():
            self.add_log("前置文件安装成功！")
            # 清理临时文件
            try:
                os.remove(zip_path)
                self.add_log("临时文件已清理")
            except Exception as e:
                self.add_log(f"清理临时文件失败: {str(e)}")
        else:
            self.add_log("前置文件安装失败，请检查解压是否正确")
    
    def download_java(self):
        """下载Java运行库"""
        self.add_log("开始下载Java运行库...")
        
        # 下载jre-legacy.zip
        zip_filename = "jre-legacy.zip"
        zip_path = os.path.join(self.launcher_dir, zip_filename)
        if not self.download_file("https://beta18jdkdownlink.ak47s.com.cn/jre-legacy.zip", zip_path):
            self.add_log("Java运行库下载失败，请检查网络连接后重试")
            return
        
        # 尝试解压
        extract_path = self.launcher_dir
        if not self.extract_zip(zip_path, extract_path):
            self.add_log("Java运行库解压失败")
            return
        
        # 检查解压是否成功
        if self.check_java():
            self.add_log("Java运行库安装成功！")
            # 清理临时文件
            try:
                os.remove(zip_path)
                self.add_log("临时文件已清理")
            except Exception as e:
                self.add_log(f"清理临时文件失败: {str(e)}")
        else:
            self.add_log("Java运行库安装失败，请检查解压是否正确")
    
    def get_java_version(self, java_path):
        """获取Java版本信息"""
        try:
            result = subprocess.run([java_path, "-version"], capture_output=True, text=True)
            if result.returncode == 0:
                output = result.stderr  # Java -version 输出到stderr
                return output
            return "未知版本"
        except:
            return "无法获取版本"
    
    def setup_skin(self):
        """设置皮肤"""
        skin_path = self.skin_path_var.get().strip()
        if not skin_path:
            return True
        
        if not os.path.exists(skin_path):
            self.add_log(f"错误: 找不到皮肤文件: {skin_path}")
            return False
        
        # 创建皮肤目录
        skin_dir = os.path.join(self.launcher_dir, "1.16", "skins")
        if not os.path.exists(skin_dir):
            os.makedirs(skin_dir)
        
        # 复制皮肤文件
        try:
            username = self.username_entry.get().strip()
            if not username:
                self.add_log("警告: 未输入用户名，跳过皮肤设置")
                return True
            
            dest_skin_path = os.path.join(skin_dir, f"{username}.png")
            shutil.copy2(skin_path, dest_skin_path)
            self.add_log(f"皮肤设置成功: {dest_skin_path}")
            return True
        except Exception as e:
            self.add_log(f"错误: 皮肤设置失败: {str(e)}")
            return False
    
    def launch_minecraft(self):
        """启动Minecraft"""
        username = self.username_entry.get().strip()
        if not username:
            messagebox.showerror("错误", "请输入用户名")
            return
        
        if not self.check_java():
            messagebox.showerror("错误", "未检测到Java运行库，请先下载Java")
            return
        
        if not self.check_libs():
            messagebox.showerror("错误", "未检测到前置文件，请先下载前置文件")
            return
        
        # 设置皮肤
        if not self.setup_skin():
            messagebox.showerror("错误", "皮肤设置失败")
            return
        
        self.add_log(f"正在启动Minecraft 1.16，用户: {username}")
        
        # 构建启动命令（使用launcher.py所在目录的路径）
        jar_path = os.path.join(self.launcher_dir, "1.16", "1.16.jar")
        natives_path = os.path.join(self.launcher_dir, "libs", "1.16-natives")
        game_dir = os.path.join(self.launcher_dir, "1.16")
        assets_dir = os.path.join(self.launcher_dir, "assets")
        assets_indexes_dir = os.path.join(assets_dir, "indexes")
        assets_objects_dir = os.path.join(assets_dir, "objects")
        assets_virtual_dir = os.path.join(assets_dir, "virtual")
        
        # 确保必要的目录存在
        required_dirs = [
            assets_dir,
            assets_indexes_dir,
            assets_objects_dir,
            os.path.join(assets_objects_dir, "00"),
            assets_virtual_dir
        ]
        
        for directory in required_dirs:
            if not os.path.exists(directory):
                os.makedirs(directory, exist_ok=True)
                self.add_log(f"创建目录: {directory}")
        
        # 检查OpenAL文件是否存在
        openal_files = ["OpenAL.dll", "OpenAL32.dll"]
        for openal_file in openal_files:
            openal_path = os.path.join(natives_path, openal_file)
            if not os.path.exists(openal_path):
                self.add_log(f"警告: 缺少音频文件: {openal_path}")
            else:
                self.add_log(f"找到音频文件: {openal_path}")
        
        # 检查游戏目录结构
        if not os.path.exists(os.path.join(game_dir, "saves")):
            os.makedirs(os.path.join(game_dir, "saves"), exist_ok=True)
            self.add_log(f"创建saves目录")
        
        if not os.path.exists(os.path.join(game_dir, "resourcepacks")):
            os.makedirs(os.path.join(game_dir, "resourcepacks"), exist_ok=True)
            self.add_log(f"创建resourcepacks目录")
        
        if not os.path.exists(os.path.join(game_dir, "shaderpacks")):
            os.makedirs(os.path.join(game_dir, "shaderpacks"), exist_ok=True)
            self.add_log(f"创建shaderpacks目录")
        
        # 检查游戏文件是否存在
        if not os.path.exists(jar_path):
            self.add_log(f"错误: 找不到Minecraft主文件: {jar_path}")
            messagebox.showerror("错误", f"找不到Minecraft主文件: {jar_path}")
            return
        
        # 构建类路径
        classpath_components = []
        
        # 添加所有库文件，确保不重复
        libs_dir = os.path.join(self.launcher_dir, "libs")
        added_jars = set()
        
        # 首先添加主游戏jar
        classpath_components.append(jar_path)
        added_jars.add(os.path.basename(jar_path))
        
        # 遍历libs目录下的所有子目录
        for root, dirs, files in os.walk(libs_dir):
            for file in files:
                if file.endswith(".jar"):
                    jar_name = os.path.basename(file)
                    if jar_name not in added_jars:
                        jar_file = os.path.join(root, file)
                        classpath_components.append(jar_file)
                        added_jars.add(jar_name)
                        self.add_log(f"添加库文件: {jar_file}")
        
        classpath = ";".join(classpath_components)
        self.add_log(f"构建的类路径长度: {len(classpath)} 字符")
        
        # 固定使用launcher目录中的Java路径
        java_paths = []
        java_exe_path = os.path.join(self.launcher_dir, "jre-legacy", "bin", "java.exe")
        javaw_exe_path = os.path.join(self.launcher_dir, "jre-legacy", "bin", "javaw.exe")
        
        # 优先使用javaw.exe，然后是java.exe
        if os.path.exists(javaw_exe_path):
            java_paths.append(javaw_exe_path)
        elif os.path.exists(java_exe_path):
            java_paths.append(java_exe_path)
        
        # 获取选择的启动模式
        launch_mode = self.launch_mode.get()
        self.add_log(f"选择的启动模式: {launch_mode}")
        
        # 根据启动模式设置参数组合
        param_combinations = []
        
        if launch_mode == "basic":
            # 基础游玩模式 - 最小参数，没声音，仅英文
            self.add_log("使用基础游玩模式参数")
            param_combinations = [
                # 最小化参数，仅保证基础游玩
                [
                    "-Xmx2G",
                    f"-Djava.library.path={natives_path}",
                    "-Dminecraft.audio.forceDisabled=true",  # 禁用音频
                    "-Duser.language=en",  # 使用英文
                    "-Duser.region=US",  # 使用美国区域
                    f"-cp", classpath,
                    "net.minecraft.client.main.Main",
                    "--username", username,
                    "--version", "1.16",
                    "--gameDir", game_dir,
                    "--assetsDir", assets_dir,
                    "--assetIndex", "1.16",
                    "--uuid", "000000000000300D9F7B6C5A90C6856A",
                    "--accessToken", "0",
                    "--userType", "offline"
                ]
            ]
        else:
            # 全量版 - 完整参数，支持音频和中文
            self.add_log("使用全量版参数")
            param_combinations = [
                # 完整参数组合，增强音频和语言相关参数
                [
                    "-XX:+UseG1GC",
                    "-XX:-UseAdaptiveSizePolicy",
                    "-XX:-OmitStackTraceInFastThrow",
                    "-Djdk.lang.Process.allowAmbiguousCommands=true",
                    "-Dfml.ignoreInvalidMinecraftCertificates=True",
                    "-Dfml.ignorePatchDiscrepancies=True",
                    "-Dlog4j2.formatMsgNoLookups=true",
                    "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump",
                    "-Xmn537m",
                    "-Xmx3584m",
                    f"-Djava.library.path={natives_path}",
                    "-Dminecraft.launcher.brand=PCL",
                    "-Dminecraft.launcher.version=381",
                    "-Dminecraft.audio.forceDisabled=false",  # 确保音频启用
                    "-Dminecraft.audio.device=OpenAL",  # 指定音频设备
                    "-Dminecraft.audio.sounds=true",  # 启用音效
                    "-Dminecraft.audio.music=true",  # 启用音乐
                    "-Duser.language=zh",  # 设置默认语言为中文
                    "-Duser.region=CN",  # 设置区域为中国
                    "-Djava.util.locale.providers=CLDR,JRE,HOST",  # 语言提供者
                    "-Dfile.encoding=UTF-8",  # 文件编码
                    f"-cp", classpath,
                    "net.minecraft.client.main.Main",
                    "--username", username,
                    "--version", "1.16",
                    "--gameDir", game_dir,
                    "--assetsDir", assets_dir,
                    "--assetIndex", "1.16",
                    "--uuid", "000000000000300D9F7B6C5A90C6856A",
                    "--accessToken", "0",
                    "--userType", "offline",
                    "--width", "854",
                    "--height", "480"
                ],
                # 简化版参数
                [
                    "-Xmx3584m",
                    "-Xmn537m",
                    f"-Djava.library.path={natives_path}",
                    "-Dminecraft.audio.forceDisabled=false",  # 确保音频启用
                    "-Duser.language=zh",  # 设置默认语言为中文
                    "-Duser.region=CN",  # 设置区域为中国
                    "-Dfile.encoding=UTF-8",  # 文件编码
                    f"-cp", classpath,
                    "net.minecraft.client.main.Main",
                    "--username", username,
                    "--version", "1.16",
                    "--gameDir", game_dir,
                    "--assetsDir", assets_dir,
                    "--assetIndex", "1.16",
                    "--uuid", "000000000000300D9F7B6C5A90C6856A",
                    "--accessToken", "0",
                    "--userType", "offline",
                    "--width", "854",
                    "--height", "480"
                ]
            ]
        
        # 尝试所有组合
        success = False
        for java_path in java_paths:
            self.add_log(f"尝试使用Java路径: {java_path}")
            
            # 获取Java版本信息
            version_info = self.get_java_version(java_path)
            self.add_log(f"Java版本信息: {version_info}")
            
            for i, params in enumerate(param_combinations):
                command = [java_path] + params
                self.add_log(f"尝试启动命令 #{i+1}: {' '.join(command)}")
                
                try:
                    # 启动进程
                    process = subprocess.Popen(
                        command,
                        cwd=game_dir,  # 在游戏目录中运行
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        shell=False
                    )
                    
                    # 等待几秒钟，检查进程是否仍在运行
                    import time
                    time.sleep(5)  # 增加等待时间，确保有足够时间初始化
                    
                    if process.poll() is None:
                        # 进程仍在运行，启动成功
                        self.add_log("Minecraft启动成功！")
                        success = True
                        # 关闭管道
                        process.stdout.close()
                        process.stderr.close()
                        break
                    else:
                        # 进程已退出，读取错误信息
                        stdout, stderr = process.communicate(timeout=10)
                        stdout_str = stdout.decode('gbk', errors='ignore') or stdout.decode('utf-8', errors='ignore')
                        stderr_str = stderr.decode('gbk', errors='ignore') or stderr.decode('utf-8', errors='ignore')
                        self.add_log(f"启动失败，标准输出: {stdout_str}")
                        self.add_log(f"启动失败，错误信息: {stderr_str}")
                except subprocess.TimeoutExpired:
                    self.add_log("读取进程输出超时")
                    process.kill()
                except Exception as e:
                    self.add_log(f"启动失败: {str(e)}")
                    import traceback
                    self.add_log(f"异常堆栈: {traceback.format_exc()}")
            
            if success:
                break
        
        if not success:
            self.add_log("所有启动尝试都失败了")
            messagebox.showerror("错误", 
                "Minecraft启动失败！\n\n"
                "可能的原因:\n"
                "1. Java版本不兼容（建议使用Java 8）\n"
                "2. 游戏文件不完整\n"
                "3. 前置文件缺失或版本不匹配\n"
                "4. 启动参数设置错误\n\n"
                "请确保:\n"
                "1. 已安装Java 8版本\n"
                "2. 1.16文件夹包含完整的游戏文件\n"
                "3. 已下载并解压前置文件\n"
                "4. 检查日志输出以获取详细错误信息")

if __name__ == "__main__":
    root = tk.Tk()
    app = MinecraftLauncher(root)
    root.mainloop()
